/**
 * 
 */
package com.et114.components.acegi.exception;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.AcegiSecurityException;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationTrustResolver;
import org.acegisecurity.AuthenticationTrustResolverImpl;
import org.acegisecurity.InsufficientAuthenticationException;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.ui.AbstractProcessingFilter;
import org.acegisecurity.ui.AccessDeniedHandler;
import org.acegisecurity.ui.AccessDeniedHandlerImpl;
import org.acegisecurity.ui.AuthenticationEntryPoint;
import org.acegisecurity.ui.ExceptionTranslationFilter;
import org.acegisecurity.ui.savedrequest.SavedRequest;
import org.acegisecurity.util.PortResolver;
import org.acegisecurity.util.PortResolverImpl;
import org.acegisecurity.util.UrlUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.PathMatcher;

/**
 * @author guanhw
 */
public class AwCasExceptionTranslationFilter implements Filter, InitializingBean {

    //~ Static fields/initializers =====================================================================================
	
	private static final Log			logger						= LogFactory
																			.getLog ( ExceptionTranslationFilter.class );
	private final PathMatcher			pathMatcher					= new AntPathMatcher ( );
	// ~ Instance fields
	// ================================================================================================
	
	private AccessDeniedHandler			accessDeniedHandler			= new AccessDeniedHandlerImpl ( );
	private AuthenticationEntryPoint	authenticationEntryPoint;
	private AuthenticationTrustResolver	authenticationTrustResolver	= new AuthenticationTrustResolverImpl ( );
	private PortResolver				portResolver				= new PortResolverImpl ( );
	private boolean						createSessionAllowed		= true;
	//extends propertis
	private String                      splitStr                    = "," ; 
	private String						excludeUrls;

	//~ Methods ========================================================================================================

	public void afterPropertiesSet() throws Exception {
		//Assert.notNull (authenticationEntryPoints , "authenticationEntryPoints must be specified" ) ;
		Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint must be specified");
		Assert.notNull(portResolver, "portResolver must be specified");
		Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver must be specified");
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
			ServletException {
		if (!(request instanceof HttpServletRequest)) {
			throw new ServletException("HttpServletRequest required");
		}

		if (!(response instanceof HttpServletResponse)) {
			throw new ServletException("HttpServletResponse required");
		}

		try {
			chain.doFilter(request, response);

			if (logger.isDebugEnabled()) {
				logger.debug("Chain processed normally");
			}
		}
		catch (AuthenticationException ex) {
			handleException(request, response, chain, ex);
		}
		catch (AccessDeniedException ex) {
			handleException(request, response, chain, ex);
		}
		catch (ServletException ex) {
			if (ex.getRootCause() instanceof AuthenticationException
					|| ex.getRootCause() instanceof AccessDeniedException) {
				handleException(request, response, chain, (AcegiSecurityException) ex.getRootCause());
			}
			else {
				throw ex;
			}
		}
		catch (IOException ex) {
			throw ex;
		}
	}

	public AuthenticationEntryPoint getAuthenticationEntryPoint() {
		return authenticationEntryPoint;
	}

	public AuthenticationTrustResolver getAuthenticationTrustResolver() {
		return authenticationTrustResolver;
	}

	public PortResolver getPortResolver() {
		return portResolver;
	}

	private void handleException(ServletRequest request, ServletResponse response, FilterChain chain,
			AcegiSecurityException exception) throws IOException, ServletException {
		if (exception instanceof AuthenticationException) {
			if (logger.isDebugEnabled()) {
				logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
			}

			sendStartAuthentication(request, response, chain, (AuthenticationException) exception);
		}
		else if (exception instanceof AccessDeniedException) {
			if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {
				if (logger.isDebugEnabled()) {
					logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point",
							exception);
				}

				sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(
						"Full authentication is required to access this resource"));
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
							exception);
				}

				accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);
			}
		}
	}

	/**
	 * If <code>true</code>, indicates that <code>SecurityEnforcementFilter</code> is permitted to store the target
	 * URL and exception information in the <code>HttpSession</code> (the default).
     * In situations where you do not wish to unnecessarily create <code>HttpSession</code>s - because the user agent
     * will know the failed URL, such as with BASIC or Digest authentication - you may wish to
	 * set this property to <code>false</code>. Remember to also set the
	 * {@link org.acegisecurity.context.HttpSessionContextIntegrationFilter#allowSessionCreation}
	 * to <code>false</code> if you set this property to <code>false</code>.
	 *
	 * @return <code>true</code> if the <code>HttpSession</code> will be
	 * used to store information about the failed request, <code>false</code>
	 * if the <code>HttpSession</code> will not be used
	 */
	public boolean isCreateSessionAllowed() {
		return createSessionAllowed;
	}

	protected void sendStartAuthentication(ServletRequest request, ServletResponse response, FilterChain chain,
			AuthenticationException reason) throws ServletException, IOException {
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		SavedRequest savedRequest = new SavedRequest(httpRequest, portResolver);
		String url = UrlUtils.buildRequestUrl ( httpRequest.getServletPath ( ) ,
				httpRequest.getRequestURI ( ) , httpRequest.getContextPath ( ) ,
				httpRequest.getPathInfo ( ) , httpRequest.getQueryString ( ) );
		
		if ( excludeUrls != null ) {
			boolean mark = false;  
			if ( excludeUrls != null  ) {
				String[] vs = excludeUrls.toString ( ).split ( splitStr ) ;
				for ( int i = 0 ; i < vs.length ; i ++ ) {
					if ( vs[ i ] != null  ) {
						if ( pathMatcher.match ( vs[ i ].trim ( ) , url ) ) {
							mark = true ;
							break ; 
						}							
					}
				}
			}
			
			if ( !mark ) {
				if (createSessionAllowed) {
					// Store the HTTP request itself. Used by AbstractProcessingFilter
					// for redirection after successful authentication (SEC-29)
					httpRequest.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SAVED_REQUEST_KEY, savedRequest);
				}

				// SEC-112: Clear the SecurityContextHolder's Authentication, as the
				// existing Authentication is no longer considered valid
				SecurityContextHolder.getContext().setAuthentication(null);
				authenticationEntryPoint.commence(httpRequest, (HttpServletResponse) response, reason);					
			}
		}			
		

		
				
		if (logger.isDebugEnabled()) {
			logger.debug("Authentication entry point being called; SavedRequest added to Session: " + savedRequest);
		}


	}

	public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
		Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
		this.accessDeniedHandler = accessDeniedHandler;
	}

	public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
		this.authenticationEntryPoint = authenticationEntryPoint;
	}

	public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) {
		this.authenticationTrustResolver = authenticationTrustResolver;
	}

	public void setCreateSessionAllowed(boolean createSessionAllowed) {
		this.createSessionAllowed = createSessionAllowed;
	}

	public void setPortResolver(PortResolver portResolver) {
		this.portResolver = portResolver;
	}

	public String getSplitStr ( ) {
		return splitStr;
	}

	public void setSplitStr ( String splitStr ) {
		this.splitStr = splitStr;
	}

	public void init(FilterConfig filterConfig) throws ServletException {
    }

    public String getExcludeUrls ( ) {
		return excludeUrls;
	}

	public void setExcludeUrls ( String excludeUrls ) {
		this.excludeUrls = excludeUrls;
	}

	public void destroy() {
    }    
}
